package com.gitee.webdav.kernel;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import com.gitee.webdav.xml.*;
import com.gitee.webdav.xml.Error;
import com.google.auto.service.AutoService;
import lombok.extern.slf4j.Slf4j;
import org.smartboot.http.common.enums.HttpStatus;
import org.smartboot.http.common.utils.Mimetypes;
import org.smartboot.http.server.HttpRequest;
import org.smartboot.http.server.HttpResponse;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author hongda.li@hand-china.com
 */
@Slf4j
@AutoService(ResourceReadonlyHandler.class)
public class ResourceFinder extends ResourceReadonlyHandler {

    @Override
    protected MethodEnum method() {
        return MethodEnum.PROPFIND;
    }

    @Override
    public void handle(HttpRequest request, HttpResponse response) throws Throwable {
        @Remark("请求报文")
        String xmlStr = readUtf8(request);

        @Remark("实体对象")
        PropertiesFind properties = XmlHelper.fromXml(xmlStr, PropertiesFind.class);
        if (properties == null) {
            response.setHttpStatus(HttpStatus.BAD_REQUEST);
            writeXml(response, Error.of(xmlStr));
            return;
        }

        @Remark("请求资源")
        File target = this.path2File(request);
        if (!FileUtil.exist(target)) {
            response.setHttpStatus(HttpStatus.NOT_FOUND);
            writeXml(response, Error.of(request.getRequestURI()));
            return;
        }

        @Remark("递归深度")
        Integer maxDepth = Convert.toInt(request.getHeader(Constants.HttpHeader.DEPTH), -1);

        @Remark("资源列表")
        List<File> fileList = this.walkFiles(target, maxDepth, any -> !CharSequenceUtil.startWith(any.getName(), StrPool.DOT));

        @Remark("响应结果")
        List<Response> responseList = new ArrayList<>(fileList.size());

        for (File file : fileList) {
            if (properties.getAllProperties() != null) {
                responseList.add(this.detail(file, properties.getAllProperties().toProperties()));
            }

            if (properties.getPropertiesName() != null) {
                responseList.add(Response.of(file, new AllProperties().toProperties()));
            }

            if (properties.getProperties() != null) {
                responseList.add(this.detail(file, properties.getProperties()));
            }
        }

        response.setHttpStatus(HttpStatus.MULTI_STATUS);
        writeXml(response, new MultiStatus().setResponseList(responseList));
    }

    private Response detail(@Remark("指定资源") File file,
                            @Remark("指定属性") Properties properties) throws IOException {
        Properties result = new Properties();

        if (properties.getDisplayName() != null) {
            result.setDisplayName(FileUtil.getName(file));
        }

        if (properties.getContentLength() != null && FileUtil.isFile(file)) {
            result.setContentLength(String.valueOf(file.length()));
        }

        if (properties.getContentType() != null && FileUtil.isFile(file)) {
            result.setContentType(Mimetypes.getInstance().getMimetype(FileUtil.getName(file)));
        }

        if (properties.getLastModified() != null) {
            result.setLastModified(DateUtil.format(FileUtil.lastModifiedTime(file), DatePattern.HTTP_DATETIME_FORMAT));
        }

        if (properties.getCreationdate() != null) {
            BasicFileAttributes attrs = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
            result.setCreationdate(DateUtil.format(DateUtil.date(attrs.creationTime().toInstant()), DatePattern.HTTP_DATETIME_FORMAT));
        }

        if (properties.getResourceType() != null) {
            if (FileUtil.isFile(file)) {
                result.setResourceType(new ResourceType());
            } else {
                result.setResourceType(new ResourceType().setCollection(CharSequenceUtil.EMPTY));
            }
        }

        return Response.of(file, result);
    }

    private List<File> walkFiles(File target, Integer depth, Predicate<File> filter) {
        if (!FileUtil.exist(target)) {
            return Collections.emptyList();
        }

        if (!FileUtil.isDirectory(target)) {
            return Collections.singletonList(target);
        }

        List<File> fileList = new ArrayList<>();
        if (filter.test(target)) {
            fileList.add(target);
        }

        walkFilesInner(target, depth != null && depth < 0 ? null : depth, filter, fileList);
        return fileList;
    }

    private void walkFilesInner(File target, Integer depth, Predicate<File> filter, List<File> fileList) {
        if (depth != null && depth <= 0) {
            return;
        }

        File[] files = target.listFiles();
        if (ArrayUtil.isEmpty(files)) {
            return;
        }

        for (File file : files) {
            if (filter.test(file)) {
                fileList.add(file);
            }
            if (FileUtil.isDirectory(file) && (depth == null || depth > 1)) {
                walkFilesInner(file, depth == null ? null : depth - 1, filter, fileList);
            }
        }
    }
}
